﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing.Imaging;
using System.IO.Compression;
using System.Threading;

namespace Logger
{
    public partial class FormMain : Form
    {
        FormPlayer frmPlayer = null;

        //string dPathScreen1;
        Thread th = null;
       
        List<ScreenData> scrL1 = new List<ScreenData>();
        List<ScreenData> scrL2 = new List<ScreenData>();

        List<ScreenData> scrL { get { return SaveListInd == 1 ? scrL1 : scrL2; } }

        int SaveListInd = 1; // лист, который будет сохраняется

        public FormMain()
        {
            InitializeComponent();
            Gl.dPathScreen = Application.StartupPath + "\\Screen";
            if (!Directory.Exists(Gl.dPathScreen)) Directory.CreateDirectory(Gl.dPathScreen);
            /*dPathScreen1 = Application.StartupPath + "\\Screen1";
            if (!Directory.Exists(dPathScreen1)) Directory.CreateDirectory(dPathScreen1);*/
            Gl.fPathSettings = Application.StartupPath + "\\Settings.sgs";
            Gl.sett = new Settings();
            /*if (!File.Exists(Gl.fPathSettings))*/ Gl.sett.Save();

            ScreenData.PartsLen = Gl.sett.PartsLen;

            DeleteLastHist();

            if (Gl.sett.DEBUG_MODE) ShowInTaskbar = true;
        }

        private byte[] TakeScreenShot(Screen currentScreen, out int w, out int h)
        {
            Bitmap bmp = new Bitmap(currentScreen.Bounds.Width, currentScreen.Bounds.Height, PixelFormat.Format16bppRgb555);
            w = bmp.Width;
            h = bmp.Height;
            Graphics gr = Graphics.FromImage(bmp);
            gr.CopyFromScreen(currentScreen.Bounds.X, currentScreen.Bounds.Y, 0, 0, currentScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
            gr.Dispose();

            // если ужимать до половины размеров, половиним
            if (Gl.sett.HalfSise)
            {
                Bitmap bmpH = new Bitmap(bmp, w / 2, h / 2);
                bmp.Dispose();
                bmp = bmpH;
                w = bmp.Width / 2;
                h = bmp.Height / 2;
            }
            //bmp.Save(Application.StartupPath + "\\Screen1\\Save_"+DateTime.Now.Ticks.ToString()+".gif", ImageFormat.Gif);

            byte[] ret = Bmp32ToMas(bmp);
            bmp.Dispose();
            return ret;
        }

        byte[] Bmp32ToMas(Bitmap bmp)
        {
            int pW = bmp.Width;
            int pH = bmp.Height;

            // Блокируем битмап с копированием его данных в массив 
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);

            // Get the address of the first line.
            IntPtr ptr = bmpData.Scan0;

            // Declare an array to hold the bytes of the bitmap.
            int bytes = bmpData.Stride * bmp.Height;
            byte[] rgbValues = new byte[bytes];
            // Копируем битмап в массив байт.
            System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

            // Разблокируем битмап без копирования
            bmp.UnlockBits(bmpData);

            return rgbValues;
        }

        ScreenData BuildNewScreenData()
        {
            int w, h;
            byte[] rgbValues = TakeScreenShot(Screen.PrimaryScreen, out w, out h);
            int lenPart = rgbValues.Length / ScreenData.PartsLen;

            byte[] ind = new byte[ScreenData.PartsLen]; // список актуальных частей, начиная от ключевого кадра
            Part[] prtL = new Part[ScreenData.PartsLen]; // вновь добавленные части
            bool bAddPart;
            byte[] actPart = null;
            int lastInd = ScreenData.PartsLen - 1; // для сокращения записи

            #region *** все части, кроме последней ***
            for (int i = 0; i < lastInd; i++)
            {
                prtL[i] = new Part();
                actPart = BuildOnePart(rgbValues, i, i * lenPart, lenPart, out bAddPart);

                // если часть отличается
                if (bAddPart) prtL[i].mas = actPart;

                // присваиваем индекс
                ind[i] = (byte)scrL.Count;
                if (!bAddPart) ind[i] = scrL[scrL.Count - 1].ind[i];
            }
            #endregion *** все части, кроме последней ***

            #region *** последняя часть ***
            int lenLastPart = rgbValues.Length - (lenPart * lastInd);
            prtL[lastInd] = new Part();
            actPart = BuildOnePart(rgbValues, lastInd, lastInd * lenPart, lenLastPart, out bAddPart);
            // если часть отличается
            if (bAddPart) prtL[lastInd].mas = actPart;
            // присваиваем индекс
            ind[lastInd] = (byte)scrL.Count;
            if (!bAddPart) ind[lastInd] = scrL[scrL.Count - 1].ind[lastInd];
            #endregion *** последняя часть ***

            return new ScreenData(prtL, ind, lenPart, lenLastPart, w, h, DateTime.Now);
        }

        byte[] BuildOnePart(byte[] rgbValues, int i, int startInd, int lenPart, out bool bAddPart)
        {
            byte[] buf = new byte[lenPart]; // массив одной части
            Array.Copy(rgbValues, startInd, buf, 0, lenPart); // заполняем массив текущей части

            byte[] lastBuf = null; // массив актуальной части для сравнения с текущей
            // если  это не первый элемент (не ключевой кадр)
            if (scrL.Count > 0)
            {
                ScreenData lastScr = scrL[scrL.Count - 1];
                // если в предыдущем массиве размер части совпадает с размером текущей части
                if (lastScr.lenPart == lenPart)
                {
                    lastBuf = lastScr.GetActPart(i, scrL); // актуальная часть с предыдущих кадров
                }
            }

            // смотрим, нужно ли обновлять эту часть (изменилась ли она). Если кадр ключевой или отличается размер части (lastBuf==null), нужно добавлять всегда
            bAddPart = true; if (lastBuf != null) bAddPart = !buf.SequenceEqual(lastBuf);

            return buf;
        }

        int cnt = 0;
        DateTime dtFPS = DateTime.Now;
        private void timer1_Tick(object sender, EventArgs e)
        {
            tmrMain.Stop();

           

            // обнуление потока сохранения, если он завершен
            if (th != null)
            {
                if (th.ThreadState != ThreadState.Running)
                {
                    th = null;
                }
            }

            // сохраняем только до bufLen записей
            if (scrL.Count <  Gl.sett.BufLen)
            {
                scrL.Add(BuildNewScreenData());
            }

            // если bufLen записей накопилось
            if (scrL.Count ==  Gl.sett.BufLen)
            {
                // если не успел сохраниться, ждем
                if (th != null)
                {
                    tmrMain.Start();
                    return;
                }

                th = new Thread(new ParameterizedThreadStart(SaveData));
                th.Start(scrL);

                // меняем буфер
                SaveListInd = SaveListInd == 1 ? 2 : 1;
                scrL.Clear(); // обнуляем новый буфер
            }

            // FPS
            DateTime dt = DateTime.Now;
            Text = (1 / (dt - dtFPS).TotalSeconds).ToString("F1") + "  " + scrL.Count.ToString() + "  " + (th != null ? "save" : "");
            dtFPS = dt;

            GC.Collect();
            cnt++;
            tmrMain.Start();
        }

        void SaveData(object o)
        {
            List<ScreenData> saveL = (List<ScreenData>)o;

            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);

            #region*** запись данных в массив *************
            bw.Write(saveL.Count); // количество кадров
            for (int i = 0; i < saveL.Count; i++)
            {
                bw.Write(saveL[i].dt.Ticks); // время
                bw.Write(saveL[i].w); // ширина
                bw.Write(saveL[i].h); // высота
                bw.Write(saveL[i].lenPart); // длина одной части
                bw.Write(saveL[i].lenLastPart); // длина последней части
                int pLen = saveL[i].prtL.Length; bw.Write(pLen);// количество частей
                for (int p = 0; p < pLen; p++)
                {
                    bw.Write(((byte)(saveL[i].prtL[p].mas == null ? 0 : 1))); // существует ли часть
                    if (saveL[i].prtL[p].mas != null) bw.Write(saveL[i].prtL[p].mas); // сама часть
                }
            }
            #endregion*** запись данных в массив *********

            byte[] buf = YCmpr.Compress(ms.ToArray());
            //byte[] buf = ms.ToArray();

            //byte[] buf = ms.ToArray();
            bw.Close();
            ms.Close();
            ms.Dispose();

            string dActPath = Gl.dPathScreen + "\\" + DateTime.Today.ToString("yyyy_MM_dd");
            if (!Directory.Exists(dActPath))
            {
                Directory.CreateDirectory(dActPath);
                DeleteLastHist();
            }
            File.WriteAllBytes(dActPath + "\\Rec_" + DateTime.Now.ToString("HH_mm_ss") + ".dt", buf);
        }

        void DeleteLastHist()
        {
            List<string> dl = new List<string>(Directory.GetDirectories(Gl.dPathScreen));
            dl.Sort();
            if (dl.Count > Gl.sett.HistDaysLen)
            {
                for (int i = 0; i < (dl.Count - Gl.sett.HistDaysLen); i++)
                    Directory.Delete(dl[i], true);
            }

        }

        /*void SaveData(object o)
        {
            List<ScreenData> scL = (List<ScreenData>)o;

            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);

            #region*** запись данных в массив *************

            bw.Write(scL.Count);
            for (int i = 0; i < scL.Count; i++)
            {
                bw.Write(scL[i].dt.Ticks);
                bw.Write(scL[i].rgbValues);
            }
            #endregion*** запись данных в массив *********

            byte[] buf = YCmpr.Compress(ms.ToArray());

            //byte[] buf = ms.ToArray();
            bw.Close();
            ms.Close();
            ms.Dispose();

            File.WriteAllBytes(dPathScreen + "\\Save_" + DateTime.Now.Ticks.ToString() + ".dt", buf);
        }*/

        private void mBtnClose_Click(object sender, EventArgs e)
        {
            Close();
        }

        Thread frmTh = null;
        private void mBtnPlay_Click(object sender, EventArgs e)
        {
            if (frmTh != null)
            {
                if (frmTh.ThreadState == ThreadState.Running)
                {
                    frmPlayer.Invoke(new FormPlayer.ShowAndActivateDelegate(frmPlayer.ShowAndActivate));
                    return;
                }
            }

            frmTh = new Thread(new ThreadStart(FrmPlayerShow));
            frmTh.Start();
        }

        void FrmPlayerShow()
        {
            frmPlayer = new FormPlayer();
            frmPlayer.ShowDialog();
            frmPlayer.Dispose();
            frmPlayer = null;
        }

        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(frmPlayer!=null)
                frmPlayer.Invoke(new FormPlayer.ShowAndActivateDelegate(frmPlayer.CloseForm));
        }

    }
}
